﻿//-----------------------------------------------------------------------
// <copyright file="SmartManipulator.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.Base.Manipulator.Smart
{
    using System;
    using System.Collections.Generic;
    using TileTool.Base.Manipulator;
    using TileTool.Common.Math;
    using TileTool.Editor.Tool.Scene;
    using TileTool.Input;
    using TileTool.Input.Focus;
    using TileTool.Input.Keyboard;
    using TileTool.Scene.Entity;
    using TileTool.Settings;

    /// <summary>
    /// Smart manipulator.
    /// </summary>
    public partial class SmartManipulator : ManipulatorBase
    {
        #region Fields - Private

        /// <summary>
        /// Settings object for the smart manipulator.
        /// </summary>
        private SmartManipulatorSettings settings;

        /// <summary>
        /// Type of manipulation that is happening.
        /// </summary>
        private SmartManipulateType manipType = SmartManipulateType.None;

        /// <summary>
        /// Handles that we manage.
        /// </summary>
        private IDictionary<int, ManipulatorHandle> handles = new Dictionary<int, ManipulatorHandle>();

        /// <summary>
        /// Tracks whether we are in size mode or translate mode for when the
        /// NSEW etc handles are used.
        /// </summary>
        private bool sizeMode = true;

        /// <summary>
        /// true if we should only translate on the X axis.
        /// </summary>
        private bool translateXOnly = false;

        /// <summary>
        /// true if we should only translate on the Y axis.
        /// </summary>
        private bool translateYOnly = false;

        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor.
        /// </summary>
        public SmartManipulator()
        {
            this.settings = SettingsSystem.Get<SmartManipulatorSettings>();

            // Setup rotate handles before size so that they take priority.
            this.Rotate_SetupHandles();
            this.Size_SetupHandles();
            this.Toggle_SetupHandles();
            this.Translate_SetupHandles();
        }

        #endregion

        #region Properties - Public

        /// <summary>
        /// Gets the type of manipulation that is currently happening.
        /// </summary>
        public SmartManipulateType ManipulateType
        {
            get
            {
                return this.manipType;
            }
        }

        #endregion

        #region Methods - Public

        /// <summary>
        /// Reset the manipulator back to a default state.
        /// </summary>
        public override void Reset()
        {
            base.Reset();

            this.Rotate_Reset();
            this.Size_Reset();
            this.Toggle_Reset();
            this.Translate_Reset();
        }

        #endregion

        #region Methods - Protected

        /// <summary>
        /// Called when the manipulator's matrix changes.
        /// </summary>
        protected override void MatrixChanged()
        {
            base.MatrixChanged();
        }

        /// <summary>
        /// Called when the manipulator's size changes.
        /// </summary>
        protected override void SizeChanged()
        {
            base.SizeChanged();

            this.Rotate_UpdateHandlePositions();
            this.Size_UpdateHandlePositions();
            this.Toggle_UpdateHandlePositions();
            this.Translate_UpdateHandlePositions();
            this.UpdateHandleVisibility();
        }

        /// <summary>
        /// Add a handle to the list of handles we are managing.
        /// </summary>
        /// <param name="handle">Manipulator handle to add.</param>
        protected override void AddManipulatorHandle(ManipulatorHandle handle)
        {
            base.AddManipulatorHandle(handle);

            if (handle.Id == -1)
            {
                throw new ArgumentException("Attempting to add a handle with an invalid ID.");
            }

            if (this.handles.ContainsKey(handle.Id))
            {
                throw new ArgumentException("Handle ID is already in use: " + handle.Id);
            }

            this.handles[handle.Id] = handle;
        }

        /// <summary>
        /// Called when a drag begins.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="canvasPos">Canvas position position where the drag started.</param>
        protected override void DragBegin(SceneToolInputData sceneInputData, Vector2D canvasPos)
        {
            base.DragBegin(sceneInputData, canvasPos);

            switch ((SmartManipulatorHandlePosition)DragHandle.Id)
            {
                case SmartManipulatorHandlePosition.North:
                case SmartManipulatorHandlePosition.South:
                case SmartManipulatorHandlePosition.East:
                case SmartManipulatorHandlePosition.West:
                case SmartManipulatorHandlePosition.NorthWest:
                case SmartManipulatorHandlePosition.NorthEast:
                case SmartManipulatorHandlePosition.SouthWest:
                case SmartManipulatorHandlePosition.SouthEast:
                    if (this.sizeMode)
                    {
                        this.manipType = SmartManipulateType.Size;
                        this.Size_DragBegin(sceneInputData, canvasPos);
                    }
                    else
                    {
                        this.manipType = SmartManipulateType.Translate;
                        this.Translate_DragBegin(sceneInputData, canvasPos);
                    }
                    
                    break;
                case SmartManipulatorHandlePosition.Rotate:
                    this.manipType = SmartManipulateType.Rotate;
                    this.Rotate_DragBegin(sceneInputData, canvasPos);
                    break;
                case SmartManipulatorHandlePosition.RotateCenter:
                    this.manipType = SmartManipulateType.RotateCenterAdjust;
                    this.Rotate_DragBegin(sceneInputData, canvasPos);
                    break;
                case SmartManipulatorHandlePosition.SizeTranslateToggle:
                    this.manipType = SmartManipulateType.SizeTranslateToggle;
                    this.Toggle_DragBegin(sceneInputData, canvasPos);
                    break;
                case SmartManipulatorHandlePosition.Translate:
                    this.manipType = SmartManipulateType.Translate;
                    this.Translate_DragBegin(sceneInputData, canvasPos);
                    break;
            }

            this.UpdateHandleVisibility();
        }

        /// <summary>
        /// Called when a drag ends successfully.
        /// 
        /// Drags which end unsuccessfully will call AbortDrag().
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <returns>Returns true if we actually dragged and modified objects.</returns>
        protected override bool DragEnd(SceneToolInputData sceneInputData)
        {
            bool forceReturnModify = false;

            switch (this.ManipulateType)
            {
                case SmartManipulateType.Rotate:
                    this.Rotate_DragEnd();
                    break;
                case SmartManipulateType.RotateCenterAdjust:
                    this.Rotate_DragEnd();
                    forceReturnModify = true;
                    break;
                case SmartManipulateType.Size:
                    this.Size_DragEnd();
                    break;
                case SmartManipulateType.SizeTranslateToggle:
                    this.Toggle_DragEnd();
                    forceReturnModify = true;
                    break;
                case SmartManipulateType.Translate:
                    this.Translate_DragEnd();
                    break;
            }

            bool res = base.DragEnd(sceneInputData);

            // Clear this after end so that throughout the end process
            // the code thinks we are still in the current manipulate state.
            this.manipType = SmartManipulateType.None;

            this.UpdateHandleVisibility();

            return res || forceReturnModify;
        }

        /// <summary>
        /// Called when a drag update happens.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="canvasPos">Canvas position position where the mouse is currently.</param>
        protected override void DragUpdate(SceneToolInputData sceneInputData, Vector2D canvasPos)
        {
            base.DragUpdate(sceneInputData, canvasPos);

            switch (this.ManipulateType)
            {
                case SmartManipulateType.Rotate:
                case SmartManipulateType.RotateCenterAdjust:
                    this.Rotate_DragUpdate(sceneInputData, canvasPos);
                    break;
                case SmartManipulateType.Size:
                    this.Size_DragUpdate(sceneInputData, canvasPos);
                    break;
                case SmartManipulateType.SizeTranslateToggle:
                    this.Toggle_DragUpdate(sceneInputData, canvasPos);
                    break;
                case SmartManipulateType.Translate:
                    this.Translate_DragUpdate(sceneInputData, canvasPos);
                    break;
            }
        }

        /// <summary>
        /// Handle lost focus input data.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected override InputHandled HandleLostFocus(SceneToolInputData sceneInputData, FocusInputData inputData)
        {
            base.HandleLostFocus(sceneInputData, inputData);

            this.translateXOnly = false;
            this.translateYOnly = false;

            return InputHandled.No;
        }

        /// <summary>
        /// Handle key down input data.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected override InputHandled HandleKeyDown(SceneToolInputData sceneInputData, KeyInputData inputData)
        {
            InputHandled handled = base.HandleKeyDown(sceneInputData, inputData);
            if (handled == InputHandled.Yes)
            {
                return InputHandled.Yes;
            }

            bool eat = false;
            switch (inputData.Key)
            {
                case InputKey.Z:
                    this.translateXOnly = true;
                    eat = true;
                    break;
                case InputKey.X:
                    this.translateYOnly = true;
                    eat = true;
                    break;
            }

            return eat ? InputHandled.Yes : InputHandled.No;
        }

        /// <summary>
        /// Handle key up input data.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected override InputHandled HandleKeyUp(SceneToolInputData sceneInputData, KeyInputData inputData)
        {
            InputHandled handled = base.HandleKeyUp(sceneInputData, inputData);
            if (handled == InputHandled.Yes)
            {
                return InputHandled.Yes;
            }

            bool eat = false;
            switch (inputData.Key)
            {
                case InputKey.T:
                    this.ToggleSizeMode();
                    eat = true;
                    break;
                case InputKey.Z:
                    this.translateXOnly = false;
                    eat = true;
                    break;
                case InputKey.X:
                    this.translateYOnly = false;
                    eat = true;
                    break;
            }

            return eat ? InputHandled.Yes : InputHandled.No;
        }

        #endregion

        #region Methods - Private

        /// <summary>
        /// Given a handle, set its position.
        /// </summary>
        /// <param name="handlePos">Handle to position.</param>
        /// <param name="centerPos">Center position.</param>
        private void PositionHandle(SmartManipulatorHandlePosition handlePos, Vector2D centerPos)
        {
            ManipulatorHandle handle = this.GetHandle(handlePos);

            handle.Pos = centerPos;
        }

        /// <summary>
        /// Given a handle position, get the handle associated with it.
        /// </summary>
        /// <param name="pos">Handle position.</param>
        /// <returns>Returns the handle associated with the position.</returns>
        private ManipulatorHandle GetHandle(SmartManipulatorHandlePosition pos)
        {
            return this.handles[(int)pos];
        }

        /// <summary>
        /// Called to toggle size mode on and off.
        /// </summary>
        private void ToggleSizeMode()
        {
            this.sizeMode = !this.sizeMode;

            foreach (ManipulatorHandle handle in this.Handles)
            {
                switch ((SmartManipulatorHandlePosition)handle.Id)
                {
                    case SmartManipulatorHandlePosition.North:
                    case SmartManipulatorHandlePosition.South:
                    case SmartManipulatorHandlePosition.East:
                    case SmartManipulatorHandlePosition.West:
                    case SmartManipulatorHandlePosition.NorthWest:
                    case SmartManipulatorHandlePosition.NorthEast:
                    case SmartManipulatorHandlePosition.SouthWest:
                    case SmartManipulatorHandlePosition.SouthEast:
                        break;
                    default:
                        continue;
                }

                SmartManipulatorHandle smartHandle = (SmartManipulatorHandle)handle;

                smartHandle.Ellipse = this.sizeMode;
            }

            SmartManipulatorHandle toggle = (SmartManipulatorHandle)this.GetHandle(SmartManipulatorHandlePosition.SizeTranslateToggle);

            toggle.Ellipse = !this.sizeMode;

            this.UpdateHandleVisibility();
        }

        /// <summary>
        /// Update the visibility of certain handles based on the current manipulator state.
        /// </summary>
        private void UpdateHandleVisibility()
        {
            bool disableSize = true;
            IEntityCollection selection = this.Owner.Scene.Selection;
            foreach (IEntity entity in selection)
            {
                if (entity.CanSize)
                {
                    disableSize = false;
                    break;
                }
            }

            bool enableSize = false; 
            
            enableSize |= !disableSize;
            enableSize |= !this.sizeMode;

            if (this.DragHandle != null)
            {
                enableSize |= (SmartManipulatorHandlePosition)this.DragHandle.Id == SmartManipulatorHandlePosition.RotateCenter;
            }

            this.GetHandle(SmartManipulatorHandlePosition.SouthWest).Visible = enableSize;
            this.GetHandle(SmartManipulatorHandlePosition.SouthEast).Visible = enableSize;
            this.GetHandle(SmartManipulatorHandlePosition.NorthEast).Visible = enableSize;
            this.GetHandle(SmartManipulatorHandlePosition.NorthWest).Visible = enableSize;
            this.GetHandle(SmartManipulatorHandlePosition.South).Visible = enableSize;
            this.GetHandle(SmartManipulatorHandlePosition.North).Visible = enableSize;
            this.GetHandle(SmartManipulatorHandlePosition.East).Visible = enableSize;
            this.GetHandle(SmartManipulatorHandlePosition.West).Visible = enableSize;
        }

        #endregion
    }
}
